// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// In this file:
// Scanner helpers/utils

package parser

import (
	"container/list"
	"fmt"
	goscanner "go/scanner"
	"go/token"
	"path"
	"strings"
	"unicode"
	"unicode/utf8"
)

// Pos describes a position in a source.
type Pos struct {
	Name string
	Line int
	Col  int
}

// Position returns a go/token.Position from Pos.
func (p *Pos) Position() token.Position {
	return token.Position{p.Name, 0, p.Line, p.Col}
}

type lexer struct {
	column              int
	column0             int
	current             byte
	dir                 string // initial fname sans path.Base of the same
	effFname            string // this respects the //line directives
	errors              goscanner.ErrorVector
	enableInject        bool // semicolon injection
	enableLitErrors     bool // report errors in literals
	enableUnicodeErrors bool // report invalid unicode values in \u, \U
	fname               string
	imports             Imports
	importCount         int
	importCounts        []int
	inject              bool
	line                int
	line0               int
	lint                int
	loophack            bool
	lstk                []bool // see src/cmd/gc/lex.c
	pkgname             string
	pos                 int // token text, src[pos0:pos] e.g. "a" src[0:3] == 0x22 0x61 0x22
	pos0                int
	sc                  int // start condition
	src                 []byte
	srclen              int
	tok                 int // token value, src[tok0:tok] e.g. src[1:2] == 0x61
	tok0                int
	val                 interface{}
	xgo                 bool // enables experimental language extensions
	xtop                *list.List
}

func newLexer(fname string, src []byte) *lexer {
	if len(src) == 0 {
		src = []byte{0}
	}
	dir := fname
	if strings.Contains(dir, "/") {
		dir = fname[:len(fname)-len(path.Base(fname))]
	}
	return &lexer{
		column:              1,
		current:             src[0],
		dir:                 dir,
		effFname:            fname,
		enableInject:        true,
		enableLitErrors:     true,
		enableUnicodeErrors: true,
		fname:               fname,
		line:                1,
		src:                 src,
		srclen:              len(src),
	}
}

func (l *lexer) newPos() Pos {
	return Pos{l.effFname, l.line0, l.column0}
}

func (l *lexer) fixlbrace(lbr int) {
	if lbr == tBODY {
		l.loophack = true
	}
}

// Implementation of yyLexer
func (l *lexer) Lex(lval *yySymType) (y int) {
	y = l.lex()
	lval.pos = l.newPos()
	switch y {
	case '(', '[':
		if l.loophack || len(l.lstk) != 0 {
			l.lstk = append(l.lstk, l.loophack)
			l.loophack = false
		}
	case ')', ']':
		if n := len(l.lstk) - 1; n >= 0 {
			l.loophack = l.lstk[n]
			l.lstk = l.lstk[:n]
		}
	case '{':
		if l.loophack {
			l.loophack = false
			y = tBODY
		}
	case tFOR, tIF, tSWITCH, tSELECT:
		l.loophack = true
	case tNAME:
		lval.sym = &Sym{Pos: lval.pos, Name: l.src[l.pos0:l.pos]}
	case tLITERAL:
		lval.val = l.val
	case tASOP:
		lval.lint = l.lint
	}
	return
}

func (l *lexer) lex() (y int) {
again:
	l.line0, l.column0 = l.line, l.column
	switch y = l.scan(); y {
	case 0:
		if l.inject {
			y = ';'
			l.inject = false
		}
	case '\n':
		if !l.inject {
			goto again
		}

		y = ';'
		l.inject = false
	case tXRIGHT:
		l.inject = l.xgo && l.enableInject
	case tNAME, tLITERAL, tBREAK, tCONTINUE, tFALL,
		tRETURN, tINC, tDEC, ')', ']', '}':
		l.inject = l.enableInject
	case unicode.ReplacementChar:
		// nop, keep inject status intact
	default:
		l.inject = false
	}
	return
}

func (l *lexer) position0() token.Position {
	return token.Position{l.effFname, l.pos0, l.line0, l.column0}
}

func (l *lexer) position() token.Position {
	return token.Position{l.effFname, l.pos, l.line, l.column}
}

// Implementation of yyLexer
func (l *lexer) Error(e string) {
	l.errors.Error(token.Position{l.effFname, -1, l.line0, l.column0}, e)
}

func (l *lexer) error(pos Pos, e string) {
	l.errors.Error(token.Position{pos.Name, -1, pos.Line, pos.Col}, e)
}

func (l *lexer) begin(startcond int) {
	l.sc = startcond
}

func (l *lexer) getc() byte {
	l.pos++
	if l.pos >= l.srclen {
		l.current = 0
		return 0
	}

	if l.current == '\n' {
		l.line++
		l.column = 1
	} else {
		l.column++
	}
	l.current = l.src[l.pos]
	return l.current
}

func (l *lexer) getRune() rune {
	if arune, size := utf8.DecodeRune(l.src[l.pos:]); size != 0 {
		l.pos += size
		return arune
	}

	return 0
}

func (l *lexer) c(c int) int {
	l.val = INT{I: c}
	l.begin(0)
	return tLITERAL
}

func (l *lexer) s(c byte) {
	l.src[l.tok] = c
	l.tok++
	l.column0 = l.column
}

func (l *lexer) asop(i int) int {
	l.lint = i
	return tASOP
}

func (l *lexer) illegalChar(c rune, msg string, showit, force, skip bool) int {
	if force || l.enableLitErrors {
		s := ""
		if unicode.IsPrint(c) {
			switch c {
			case '\'':
				s = " '\\''"
			default:
				s = fmt.Sprintf("%q", string(c))
				s = " '" + s[1:len(s)-1] + "'"
			}
		}
		if showit {
			s = fmt.Sprintf(" %U%s", c, s)
		}
		l.error(l.newPos(), fmt.Sprintf("illegal character%s%s", msg, s))
	}
	if skip {
		l.getc()
	}
	l.begin(0)
	return unicode.ReplacementChar
}

func (l *lexer) checkRune(col0 int, arune rune) rune {
	if !l.enableUnicodeErrors {
		return arune
	}

	switch {
	case arune < 0 || (arune >= 0xd800 && arune <= 0xdfff) || arune > unicode.MaxRune:
		l.error(Pos{l.effFname, l.line0, col0}, "escape sequence is invalid Unicode code point")
	default:
		return arune
	}

	return unicode.ReplacementChar
}

func (l *lexer) scanIdent() {
	for {
		runepos := l.pos
		rune := l.getRune()
		if !(rune == '_' || unicode.IsLetter(rune) || unicode.IsDigit(rune)) {
			l.pos = runepos - 1
			l.current = l.getc()
			break
		}
	}
}
